/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "lock_manager.h"
#include "iot_list.h"
#include "iot_store_manager.h"

#define LOCK_DEBUG  1
#if LOCK_DEBUG
#define LOCK_DBG(fmt, args...)  printf("[LOCK_DEBUG][%s|%d]" fmt, __func__, __LINE__, ##args)
#define LOCK_ERR(fmt, args...)  printf("[LOCK_ERROR][%s|%d]" fmt, __func__, __LINE__, ##args)
#define LOCK_INFO(fmt, args...) printf("[LOCK_INFO ][%s|%d]" fmt, __func__, __LINE__, ##args)
#else
#define LOCK_DBG(fmt, args...)  do {} while (0)
#define LOCK_ERR(fmt, args...)  do {} while (0)
#define LOCK_INFO(fmt, args...) do {} while (0)
#endif

#define DISPOSABLE_LENGTH   4
#define TIME_UINIT          60

#define LOCK_DEF_PSK        "888888"

static unsigned char g_isInited = 0;
static IOT_LIST g_lockDataList = NULL;

static char g_disposable[DISPOSABLE_LENGTH + 1] = {0};
static int g_startTime = -1;
static int g_endTime = -1;
static int g_weekDay = -1;

static int GetLockFromStoreManager(void)
{
#if (USER_KV_STORE)
    int total = StoreManagerGetTotal();
    if (total <= 0) {
        return -1;
    }
    LOCK_DBG("total = %d\n", total);
    for (int i = 0; i < total; i++) {
        char buff[20] = {0};
        LockInfo info = {0};

        if (StoreManagerGetData((char *)buff, sizeof(buff), i) < 0) {
            LOCK_ERR("get data from KV store failed! \n");
            return -1;
        }
        strncpy(info.id, buff, LOCK_ID_LENGTH);
        strncpy(info.password, buff+LOCK_ID_LENGTH, LOCK_PSK_LENGTH);

        IoT_ListAppend(g_lockDataList, (void *)&info, sizeof(LockInfo));
    }
#endif
    return 0;
}

static int LockManagerUpdateDisposablePsk(unsigned char ops, const char *psk, int sTime, int eTime)
{
    int ret = 0;
    switch (ops) {
        case OPTION_ADD:
        case OPTION_UPDATE:
            if (psk == NULL || strlen(psk) != DISPOSABLE_LENGTH) {
                LOCK_ERR("params error! \n");
                break;
            }
            memset(g_disposable, 0x00, sizeof(g_disposable));
            strncpy(g_disposable, psk, DISPOSABLE_LENGTH);
            g_startTime = sTime * TIME_UINIT;
            g_endTime = eTime * TIME_UINIT;
            ret = 1;
            break;
        case OPTION_DELETE:
            memset(g_disposable, 0x00, sizeof(g_disposable));
            g_startTime = 0;
            g_endTime = 0;
            ret = 1;
            break;
        default:
            LOCK_ERR("option error! no such option!! \n");
            break;
    }

    return ret;
}

static int LockManagerUpdateLockPsk(unsigned char ops, LockInfo *info)
{
    int ret = 0;
    char buff[20] = {0};
    switch (ops) {
        case OPTION_ADD:
            LOCK_DBG("ADD PSK\n");
            if (IoT_ListAppend(g_lockDataList, info, sizeof(LockInfo)) < 0) {
                LOCK_ERR("IoT_ListAppend failed!! \n");
                break;
            }
            ret = 1;
#if (USER_KV_STORE)
            strcpy(buff, info->id);
            strcat(buff, info->password);
            if (StoreManagerAddData((const char *)buff, strlen(buff)) < 0) {
                LOCK_ERR("StoreManagerAddData failed!! \n");
            }
#endif
            break;
        case OPTION_UPDATE:
            LOCK_DBG("UPDATE PSK!\n");
            if (IoT_ListGetSize(g_lockDataList) <= 0) {
                ret = LockManagerUpdateLockPsk(OPTION_ADD, info);
                break;
            }

            for (int i = 0; i < IoT_ListGetSize(g_lockDataList); i++) {
                LockInfo tmpInfo = {0};
                if (IoT_ListGet(g_lockDataList, i, &tmpInfo, sizeof(LockInfo)) < 0) {
                    break;
                }
                if (strcmp(info->id, tmpInfo.id) == 0) {
                    if (IoT_ListUpdate(g_lockDataList, i, info, sizeof(LockInfo)) < 0) {
                        LOCK_ERR("IoT_ListUpdate listdata[%d] failed!! \n", i);
                        break;
                    }
                    ret = 1;
#if (USER_KV_STORE)
                    strcpy(buff, info->id);
                    strcat(buff, info->password);
                    if (StoreManagerUpdateData(i, (const char *)buff, strlen(buff)) < 0) {
                        LOCK_ERR("StoreManagerUpdateData %d data failed!! \n", i);
                    }
#endif
                    break;
                }
            }
            break;
        case OPTION_DELETE:
            LOCK_DBG("DELETE PSK!\n");
            if (IoT_ListDelete(g_lockDataList, info) < 0) {
                LOCK_ERR("IoT_ListDelete failed!! \n");
                break;
            }
            ret = 1;
#if (USER_KV_STORE)
            strcpy(buff, info->id);
            strcat(buff, info->password);
            if (StoreManagerDeleteData((const char *)buff) < 0) {
                LOCK_ERR("StoreManagerDeleteData failed!! \n");
            }
#endif
            break;
        default:
            LOCK_ERR("option error! no such option!! \n");
            break;
    }
    printf("ret = %d\n", ret);
    return ret;
}

static int CheckDisposablePsk(const char *psk, int weekday, int times)
{
    if (weekday != g_weekDay) {
        LOCK_ERR("the disposable PSK is out of day! \n");
        return 0;
    }

    if (times < g_startTime || times > g_endTime) {
        LOCK_ERR("the disposable PSK is out of time! \n");
        return 0;
    }

    if (strcmp(psk, (const char *)g_disposable) == 0) {
        return 1;
    }

    LOCK_ERR("the disposable PSK is incorrect! \n");

    return 0;
}

static int CheckLockPsk(const char *psk)
{
    int ret = 0;
    int total;

    if (psk == NULL || strlen(psk) != LOCK_PSK_LENGTH) {
        LOCK_ERR("psk format error! \n");
        return ret;
    }
    total = IoT_ListGetSize(g_lockDataList);
    if (total <= 0) {
        ret = strcmp(psk, (const char *)LOCK_DEF_PSK);
        return ret == 0 ? 1 : 0;
    }

    for (int i = 0; i < total; i++) {
        LockInfo info = {0};
        if (IoT_ListGet(g_lockDataList, i, &info, sizeof(info)) < 0) {
            LOCK_ERR("get lock from list failed! \n");
            break;
        }
        if (strcmp(psk, (const char *)info.password) == 0) {
            ret = 1;
            break;
        }
    }

    return ret;
}

int LockManagerInit(int curWeekDay)
{
    if (g_isInited) {
        return 1;
    }
    LOCK_DBG("curWeekDay = %d \n", curWeekDay);
    g_lockDataList = IoT_ListCreate(NULL, 0);
    if (g_lockDataList == NULL) {
        LOCK_ERR("IoT_ListCreate failed! \n");
        return -1;
    }

    LOCK_DBG("before GetLockFromStoreManager \n");
    GetLockFromStoreManager();
    LOCK_DBG("after GetLockFromStoreManager \n");
    g_weekDay = curWeekDay;
    g_isInited = 1;

    return 0;
}

int LockManagerUpdate(CommandParamSetPsk *paramPsk)
{
    int result = 0;

    if (paramPsk == NULL) {
        LOCK_ERR("NULL POINT! \n");
        return result;
    }

    if (paramPsk->isDisposable) {
        result = LockManagerUpdateDisposablePsk(paramPsk->option, paramPsk->password,
                                                paramPsk->startTime, paramPsk->endTime);
    } else {
        LockInfo info = {0};
        if (strlen(paramPsk->id) > LOCK_ID_LENGTH || strlen(paramPsk->password) != LOCK_PSK_LENGTH) {
            LOCK_ERR("id || password length error!! \n");
            return 0;
        }
        strcpy(info.id, paramPsk->id);
        strcpy(info.password, paramPsk->password);
        result = LockManagerUpdateLockPsk(paramPsk->option, &info);
    }
    LOCK_INFO("result = %d \n", result);
    return result;
}

int LockManagerCheckup(LockCheckInfo *info)
{
    int result;

    if (info->disposable) {
        result = CheckDisposablePsk((const char *)(info->mLock.password), info->curDay, info->curSeconds);
    } else {
        result = CheckLockPsk((const char *)(info->mLock.password));
    }
    LOCK_INFO("result = %d \n", result);
    return result;
}

int LockManagerGetPskNum(void)
{
    if (g_lockDataList == NULL) {
        return -1;
    }

    return IoT_ListGetSize(g_lockDataList);
}

int LockManagerClearPsk(void)
{
    if (g_lockDataList == NULL) {
        return -1;
    }

    IoT_ListClear(g_lockDataList);
#if (USER_KV_STORE)
    StoreManagerDelete();
#endif
}

void LockManagerDeinit(void)
{
    if (g_lockDataList) {
        IoT_ListDestroy(g_lockDataList);
        g_lockDataList = NULL;
    }
    memset(g_disposable, 0x00, sizeof(g_disposable));
    g_startTime = g_endTime = -1;
}